package cn.geminis.lowcode.utils;

import cn.geminis.lowcode.compiler.Context;
import cn.geminis.lowcode.compiler.util.JdkCompilerClass;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;

import javax.lang.model.element.Element;
import javax.tools.Diagnostic;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;

/**
 * @author puddi
 */
public class Utils {

    @Getter
    @Setter
    private static String projectRootPackage;

    private Utils() {
    }

    public static String removeLastQualifiedName(String packageName) {
        var index = packageName.lastIndexOf(".");
        return packageName.substring(0, index);
    }

    public static String getElementPackageName(Element element) {
        var qualifiedName = getElementQualifiedName(element);
        return removeLastQualifiedName(qualifiedName);
    }

    @SneakyThrows
    public static String getElementQualifiedName(Element element) {
        var getQualifiedNameMethod = element.getClass().getMethod("getQualifiedName");
        return getQualifiedNameMethod.invoke(element).toString();
    }

    public static void noteMessage(String message) {
        Context.INSTANCE.getProcessingEnv().getMessager().printMessage(Diagnostic.Kind.NOTE, "Low-Code :: " + message);
    }

    @SneakyThrows
    public static <T> T callConstructor(Class<?> clazz, Class<?>[] paramTypes, Object... args) {
        var constructor = clazz.getDeclaredConstructor(paramTypes);
        constructor.setAccessible(true);
        return (T) constructor.newInstance(args);
    }

    @SneakyThrows
    public static <T> T callMethodWithTypes(Object obj, String methodName, Class<?>[] paramTypes, Object... args) {
        Method method;
        try {
            method = obj.getClass().getDeclaredMethod(methodName, paramTypes);
        } catch (NoSuchMethodException e) {
            method = obj.getClass().getMethod(methodName, paramTypes);
        }
        if (!method.canAccess(obj)) {
            method.setAccessible(true);
        }
        return (T) method.invoke(obj, args);
    }

    public static <T> T callMethod(Object obj, String methodName, Object... args) {
        var paramTypes = Arrays.stream(args).map(Object::getClass).toArray(Class[]::new);
        return callMethodWithTypes(obj, methodName, paramTypes, args);
    }

    @SneakyThrows
    public static <T> T callMethodWithTypes(Class<?> clazz, String methodName, Class<?>[] paramTypes, Object... args) {
        return (T) clazz.getDeclaredMethod(methodName, paramTypes).invoke(null, args);
    }

    public static <T> T callMethod(Class<?> clazz, String methodName, Object... args) {
        var paramTypes = Arrays.stream(args).map(Object::getClass).toArray(Class[]::new);
        return callMethodWithTypes(clazz, methodName, paramTypes, args);
    }

    @SneakyThrows
    public static <T> T getField(Object obj, String name) {
        var field = obj.getClass().getField(name);
        return (T) field.get(obj);
    }

    @SneakyThrows
    public static void setField(Object obj, String name, Object value) {
        var field = obj.getClass().getField(name);
        if (!field.canAccess(obj)) {
            field.setAccessible(true);
        }
        field.set(obj, value);
    }


    public static Object nil() {
        return Utils.callMethod(JdkCompilerClass.LIST, "nil");
    }

    public static Object listFrom(List<?> items) {
        return Utils.callMethodWithTypes(JdkCompilerClass.LIST, "from", new Class[]{Object[].class}, (Object) items.toArray());
    }

    public static String lowerFirst(String str) {
        var cs = str.toCharArray();
        cs[0] += 32;
        return String.valueOf(cs);
    }

    public static String upperFirst(String str) {
        var cs = str.toCharArray();
        cs[0] -= 32;
        return String.valueOf(cs);
    }
}
